#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstring>

using namespace std;
//////////////////////////////////////////////////////////////////////////

// 匿名管道
// 分别以读和写打开同一个文件！(父进程读写方式打开，子进程继承，一端关闭读，一端关闭写)
// 一般而言，我们管道只能用来进行单向数据通信。
// 要双向也可以，那就创建两个管道。

// int pipe(int pipefd[2]):输出型参数！读端和写端返回来

// 父进程读取，子进程写入
int main()
{
    // 第一步：创建管道文件，打开读写端
    int fds[2];
    int n = pipe(fds); // 返回0成功，-1失败
    assert(n == 0);

    // 第二步：fork
    pid_t id = fork();
    assert(id >= 0);
    if (id == 0)
    {
        // 子进程进行写入
        // string msg = "hello,i am child";
        close(fds[0]);
        const char *s = "我是子进程，我正在给你发消息";
        int cnt = 0;
        while (true)
        {
            char buffer[1024]; // 只有子进程能看到
            snprintf(buffer, sizeof buffer, "child->parent say:%s[%d][%d]", s, cnt++, getpid());
            write(fds[1], buffer, strlen(buffer)); // 不用+1，\0只有C语言认
            cout << cnt << endl;
            // sleep(1);                              // 细节，每隔一秒写一次
            //break;
        }
        // 子进程的通信代码
        // 子进程
        close(fds[1]);
        cout << "子进程关闭自己的写端" << endl;
        exit(0); // 进程退出的时候其实也会关闭文件
    }
    // 父进程进行读取
    close(fds[1]);
    // 父进程的通信代码
    while (true)
    {
        // 如果读端一直不读，写端一直在写呢？
        // 管道是一个固定大小的缓冲区，当缓冲区被写满了，就不能写了，不然信息会被覆盖，写端就会阻塞
        // sleep(100);
        sleep(2);
        char buffer[1024];
        // 如果管道中没有了数据，读端在读，默认会直接阻塞当前正在读取的进程！
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1); // 反过来，要留一个\0的位置给缓冲区，防止缓冲区溢出
        if (s > 0)
        {
            buffer[s] = 0;
            cout << "GET MESSAGE# " << buffer << "| my pid :" << getpid() << endl;
        }
        else if(s == 0)
        {
            //读到文件结尾
            cout << "read: " << s << endl;
            break;
        }
        // 细节：父进程没有sleep
        break;//父进程读一次就不读了
    }
    close(fds[0]);
    cout << "父进程关闭读端" << endl;

    int status = 0;
    n = waitpid(id, &status, 0); // 不关心子进程的退出码等，写nullptr就行
    assert(n == id);

    cout << "pid->" << n << " : " << (status & 0x7F) << endl;

    // 0,1,2 -> 3,4
    // [0]: 读取，嘴巴
    // [1]: 写入，钢笔
    // cout << "fds[0]" << fds[0] << endl;
    // cout << "fds[1]" << fds[1] << endl;
    return 0;
}

//////////////////////////////////////////////////////////////////////////

// 进程间通信目的
// 数据传输：一个进程需要将它的数据发送给另一个进程
// 资源共享：多个进程之间共享同样的资源。
// 通知事件：一个进程需要向另一个或一组进程发送消息，通知它（它们）发生了某种事件（如进程终止时要通知父进程）。
// 进程控制：有些进程希望完全控制另一个进程的执行（如Debug进程），此时控制进程希望能够拦截另
// 一个进程的所有陷入和异常，并能够及时知道它的状态改变。
// 有时候我们是需要多进程协同的！完成某种业务内容！

// 进程是具有独立性的！今天需要通信 -- 通信成本一定不低
// 该如何理解通信的本质问题：
// 1.OS需要直接或者间接给通信双方的进程提供 "内存空间"
// 2.要通信的进程，必须看到一份公共的资源！
// 不同的通信种类：本质就是上面所说的资源，是OS中的哪一个模块提供的！
// 问题：
// 1.需要让不同的进程看到同一份资源
// 2.通信

/////////////////////////////////////////////////////////////////////////

// 进程间通信发展
// 管道：基于文件系统的(匿名管道 + 命名管道)
// POSIX进程间通信    -> 让通信过程可以跨主机
// System V进程间通信 -> 聚焦在本地通信 (共享内存.主要 + 消息队列 + 信号量)

/////////////////////////////////////////////////////////////////////////

// 管道：

// 在struct file中：
// 1.file的操作方法
// 2.有属于自己的内核缓冲区！ (父进程写，子进程读，反之同理)
// 3.struct Page{}

// 通过父子进程的关系(子进程创建的时候会拷贝父进程的 task_struct + struct files_struct(struct file* fd_arry[]))
// 父子进程看到同一份内核资源：文件系统提供的  --> 所以也将管道称之为管道文件！
// 如何让两个进程看到同一个管道文件？fork创建子进程完成的！

//读写特征：
//1.读慢，写快
//2.读快，写慢
//3.写关闭，读到0
//3.读关闭，写？ OS会终止写端，会给写进程发送信号，终止写端！(因为使用一个管道的读端使用计数器管理起来的，当OS知道读端计数位0->真正关闭读端，给进程发送13号信号)

//管道的特征：
//1.管道的生命周期进程
//2.管道可以用来进行具有学院关系的进程之间进行通信，常用于父子通信
//3.管道是面向字节流的(网络)
//4.半双工 -- 单向通信(特殊概念)
//5.互斥与同步机制 -- 对共享资源进行保护的方案